package UtilityPack;

/**
 * Created by Aaron on 8/19/2014.
 */
public class PerlinGen implements NoiseGen {

    private double persistance;
    private double frequency;
    private double amplitude;
    private int octaves;
    private int seed;


    public PerlinGen ( ) {
        this(0, 0, 0, 0, 0);
    }

    public PerlinGen (double persistance, double frequency, double amplitude, int octave, int seed) {
        this.persistance = persistance;
        this.frequency = frequency;
        this.amplitude = amplitude;
        this.octaves = octave;
        this.seed = seed;
    }

    public double getHeight (double x, double y) {
        return this.amplitude * total(x, y);
    }

    private double total (double i, double j) {
        double t = 0.0;
        double amplitude = 1;
        double frequency = this.frequency;

        for (int k = 0; k < this.octaves; k++) {
            t += getValue(j * frequency + this.seed, i * frequency + this.seed) * amplitude;
            amplitude *= persistance;
            frequency *= 2;
        }

        return t;
    }

    private double interpolate (double x, double y, double z) {
        double negativeZ = 1.0 - z;
        double factor1 = 3.0 * Math.pow(negativeZ, 2) - 2.0 * Math.pow(negativeZ, 3);
        double factor2 = 3.0 * Math.pow(z, 2) - 2.0 * Math.pow(z, 3);

        return x * factor1 + y * factor2;
    }

    private double noise (int x, int y) {
        int n = x + y * 57;
        n = (n << 13) ^ n;
        int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff;
        return 1.0 - t * 0.931322574615478515625e-9;
    }

    private double getValue(double x, double y) {

        double result = 0.0;

        int xInt = (int) x;
        int yInt = (int) y;
        double xFrac = x - xInt;
        double yFrac = y - yInt;

        double n01 = noise(xInt-1, yInt-1);
        double n02 = noise(xInt+1, yInt-1);
        double n03 = noise(xInt-1, yInt+1);
        double n04 = noise(xInt+1, yInt+1);

        double n05 = noise(xInt-1, yInt);
        double n06 = noise(xInt+1, yInt);
        double n07 = noise(xInt, yInt-1);
        double n08 = noise(xInt, yInt+1);

        double n09 = noise(xInt, yInt);

        double n12 = noise(xInt+2, yInt-1);
        double n14 = noise(xInt+2, yInt+1);
        double n16 = noise(xInt+2, yInt);

        double n23 = noise(xInt-1, yInt+2);
        double n24 = noise(xInt+1, yInt+2);
        double n28 = noise(xInt, yInt+2);

        double n34 = noise(xInt+2, yInt+2);

        // Establish the noise values at the corners.
        double x0y0 = 0.0625 * (n01 + n02 + n03 + n04) + 0.125 * (n05 + n06 + n07 + n08) + 0.25 * (n09);
        double x1y0 = 0.0625 * (n07 + n12 + n08 + n14) + 0.125 * (n09 + n16 + n02 + n04) + 0.25 * (n06);
        double x0y1 = 0.0625 * (n05 + n06 + n23 + n24) + 0.125 * (n03 + n04 + n09 + n28) + 0.25 * (n08);
        double x1y1 = 0.0625 * (n09 + n16 + n28 + n34) + 0.125 * (n08 + n14 + n06 + n24) + 0.25 * (n04);

        // Interpolate between corner values.
        double v1 = interpolate(x0y0, x1y0, xFrac); //interpolate in x direction (y)
        double v2 = interpolate(x0y1, x1y1, xFrac); //interpolate in x direction (y+1)
        result = interpolate(v1, v2, yFrac);  //interpolate in y direction

        return result;
    }

    public double getPersistance() {
        return persistance;
    }

    public void setPersistance(double persistance) {
        this.persistance = persistance;
    }

    public double getFrequency() {
        return frequency;
    }

    public void setFrequency(double frequency) {
        this.frequency = frequency;
    }

    public double getAmplitude() {
        return amplitude;
    }

    public void setAmplitude(double amplitude) {
        this.amplitude = amplitude;
    }

    public int getOctaves() {
        return octaves;
    }

    public void setOctaves(int octave) {
        this.octaves = octave;
    }

    public int getSeed() {
        return seed;
    }

    public void setSeed(int seed) {
        this.seed = seed;
    }
}
